"""
src/world.py
    World class with built-in collision support

Written By:
    James Thomas
    Email: jim@houseoftechnology.org
    Web: http://mission-cognition.houseoftechnology.org/

Copyright 2009-2012

This file is part of the PandaODElib distribution.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version. The text of the
GNU Lesser General Public License is included with this library in
the file named LICENSE.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""

from pandac.PandaModules import OdeWorld, OdeJointGroup, OdeHashSpace

class World(OdeWorld):
    """An enhanced ODE world class that automatically manages collisions.
    Implemented as a singleton class so any object that needs the world can
    simply instantiate the class and get the reference to the single world.
    Insight on how to do this came from http://code.activestate.com/recipes/52558/#c8"""
    __singleInstance = None # This will hold the single instance

    def __new__(cls, *args, **kwargs):
        # Need to check to see if __singleInstance is assigned in this class
        # The trick is to compare class types instead of just testing for none.
        # This way you can subclass Primitives and it will still work.
        if cls != type(cls.__singleInstance):
            # These don't match so therefore cls.__singleInstance is None
            # and we need to create the object.
            cls.__singleInstance = OdeWorld.__new__(cls)
        return cls.__singleInstance

    def __init__(self, Display=None, Size=(640, 480), Title=''):
        try:
            # If we have already done our initialization the
            # value will be set and an exception won't be raised
            return self.__initRetValue
        except AttributeError:
            # First time running init, set our return value
            # so we do the initialization again
            self.__initRetValue = None

        OdeWorld.__init__(self)

        if Display is None:
            # Choose render for the default parent 'scene'
            self._myScene = render
        else:
            # Use the display we were provided
            self._myScene = Display

        
        # The surface table is needed for autoCollide we will setup a default
        self.initSurfaceTable(1)
        # (surfaceId1, surfaceId2, mu, bounce, bounce_vel, soft_erp, soft_cfm, slip, dampen)
        self.setSurfaceEntry(0, 0, 150, 0.0, 9.1, 0.9, 0.00001, 0.0, 0.002)
        # Use odeSpace.setSurfaceType(geometry, surfaceId) to change surfaces

        self._space = OdeHashSpace()

        # A joint group for the contact joints that are generated whenever
        # two bodies collide
        self._contactGroup = OdeJointGroup()
        
        # Use the auto collide system
        self._space.setAutoCollideJointGroup(self._contactGroup)
        self._space.setAutoCollideWorld(self)
        
    @property 
    def space(self):
        return self._space
    
    @property 
    def scene(self):
        return self._myScene
    
    def step(self, DeltaTime):
        """Performs a step with collision handling."""
        # Detect collisions and create contact joints
        self._space.autoCollide()

        # Simulation step
        OdeWorld.step(self, DeltaTime)

        # Remove all contact joints
        self._contactGroup.empty()

    def quickStep(self, DeltaTime):
        """Performs a step with collision handling."""
        # Detect collisions and create contact joints
        self._space.autoCollide()

        # Simulation step
        OdeWorld.quickStep(self, DeltaTime)

        # Remove all contact joints
        self._contactGroup.empty()

